package org.jivesoftware.openfire.redischat;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.database.SequenceManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.muc.MultiUserChatService;
import org.jivesoftware.openfire.muc.cluster.ServiceUpdatedEvent;
import org.jivesoftware.openfire.muc.spi.MUCPersistenceManager;
import org.jivesoftware.openfire.stats.StatisticsManager;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.util.AlreadyExistsException;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.NotFoundException;
import org.jivesoftware.util.cache.CacheFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.component.ComponentException;
import org.xmpp.component.ComponentManagerFactory;
import org.xmpp.packet.JID;
/**
 * 
 * 提供群聊功能的Module，注册群组聊天组件服务<br> 
 *
 */
public class RedisMultiUserChatManager extends BasicModule {

	private static final Logger Log = LoggerFactory
			.getLogger(RedisMultiUserChatManager.class);

	private static final String LOAD_SERVICES = "SELECT subdomain,description,isHidden FROM tMucService";
	private static final String CREATE_SERVICE = "INSERT INTO tMucService(serviceID,subdomain,description,isHidden) VALUES(?,?,?,?)";
	private static final String UPDATE_SERVICE = "UPDATE tMucService SET subdomain=?,description=? WHERE serviceID=?";
	private static final String DELETE_SERVICE = "DELETE FROM tMucService WHERE serviceID=?";
	private static final String LOAD_SERVICE_ID = "SELECT serviceID FROM tMucService WHERE subdomain=?";
	private static final String LOAD_SUBDOMAIN = "SELECT subdomain FROM tMucService WHERE serviceID=?";

	private static final String roomsStatKey = "muc_rooms";
	private static final String occupantsStatKey = "muc_occupants";
	private static final String usersStatKey = "muc_users";
	private static final String incomingStatKey = "muc_incoming";
	private static final String outgoingStatKey = "muc_outgoing";
	private static final String trafficStatGroup = "muc_traffic";

	private static RedisMultiUserChatManager redisMultiUserChatManager;
	/**
	 * 本地群组服务缓存
	 */
	private ConcurrentHashMap<String, RedisMultiUserChatService> mucServices = new ConcurrentHashMap<String, RedisMultiUserChatService>();

	public static RedisMultiUserChatManager getInstance(){
		if(null == redisMultiUserChatManager){
			redisMultiUserChatManager = new RedisMultiUserChatManager();
		}
		return redisMultiUserChatManager;
	}
	
	public RedisMultiUserChatManager() {
		super("Redis Multi user chat manager");
	}

	@Override
	public void start() {
		super.start();

		loadServices();

		for (RedisMultiUserChatService service : mucServices.values()) {
			registerMultiUserChatService(service);
			
		}

	}

	@Override
	public void stop() {
		super.stop();


		StatisticsManager.getInstance().removeStatistic(roomsStatKey);
		StatisticsManager.getInstance().removeStatistic(occupantsStatKey);
		StatisticsManager.getInstance().removeStatistic(usersStatKey);
		StatisticsManager.getInstance().removeStatistic(incomingStatKey);
		StatisticsManager.getInstance().removeStatistic(outgoingStatKey);

		for (RedisMultiUserChatService service : mucServices.values()) {
			unregisterMultiUserChatService(service.getServiceName());
		}
	}

	/**
	 * 
	 * 注册群组服务组件
	 *
	 * @param service
	 */
	public void registerMultiUserChatService(RedisMultiUserChatService service) {
		Log.debug("MultiUserChatManager: Registering MUC service "
				+ service.getServiceName());
		try {
			ComponentManagerFactory.getComponentManager().addComponent(
					service.getServiceName(), service);
			mucServices.put(service.getServiceName(), service);
		} catch (ComponentException e) {
			Log.error(
					"MultiUserChatManager: Unable to add "
							+ service.getServiceName() + " as component.", e);
		}
	}

	/**
	 * 
	 * subdomain = RedisMultiUserChatService.getServiceName()
	 *
	 * @param subdomain
	 */
	public void unregisterMultiUserChatService(String subdomain) {
		Log.debug("MultiUserChatManager: Unregistering MUC service "
				+ subdomain);
		RedisMultiUserChatService service = mucServices.get(subdomain);
		if (service != null) {
			service.shutdown();
			try {
				ComponentManagerFactory.getComponentManager().removeComponent(
						subdomain);
			} catch (ComponentException e) {
				Log.error("MultiUserChatManager: Unable to remove " + subdomain
						+ " from component manager.", e);
			}
			mucServices.remove(subdomain);
		}
	}

	public Integer getServicesCount(boolean includePrivate) {
		Integer servicesCnt = mucServices.size();
		if (!includePrivate) {
			for (RedisMultiUserChatService service : mucServices.values()) {
				if (service.isHidden()) {
					servicesCnt--;
				}
			}
		}
		return servicesCnt;
	}

	/**
	 * 
	 * 创建聊天室
	 *
	 * @param subdomain
	 * @param description
	 * @param isHidden
	 * @return
	 * @throws AlreadyExistsException
	 */
	public RedisMultiUserChatServiceImpl createMultiUserChatService(
			String subdomain, String description, Boolean isHidden)
			throws AlreadyExistsException {
		if (getMultiUserChatServiceID(subdomain) != null)
			throw new AlreadyExistsException();
		RedisMultiUserChatServiceImpl muc = new RedisMultiUserChatServiceImpl(subdomain,
				description, isHidden);
		insertService(subdomain, description, isHidden);
		registerMultiUserChatService(muc);
		return muc;
	}


	public void updateMultiUserChatService(Long serviceID, String subdomain,
			String description) throws NotFoundException {
		RedisMultiUserChatService muc = (RedisMultiUserChatServiceImpl) getMultiUserChatService(serviceID);
		if (muc == null)
			throw new NotFoundException();
		String oldsubdomain = muc.getServiceName();
		if (!mucServices.containsKey(oldsubdomain)) {
			throw new NotFoundException();
		}
		if (oldsubdomain.equals(subdomain)) {
			updateService(serviceID, subdomain, description);
			muc.setDescription(description);
		} else {
			unregisterMultiUserChatService(subdomain);
			updateService(serviceID, subdomain, description);
			muc = new RedisMultiUserChatServiceImpl(subdomain, description,
					muc.isHidden());
			registerMultiUserChatService(muc);
		}
	}

	public void updateMultiUserChatService(String cursubdomain,
			String newsubdomain, String description) throws NotFoundException {
		Long serviceID = getMultiUserChatServiceID(cursubdomain);
		if (serviceID == null)
			throw new NotFoundException();
		updateMultiUserChatService(serviceID, newsubdomain, description);
	}

	public void removeMultiUserChatService(String subdomain)
			throws NotFoundException {
		Long serviceID = getMultiUserChatServiceID(subdomain);
		if (serviceID == null) {
			Log.error("MultiUserChatManager: Unable to find service to remove for "
					+ subdomain);
			throw new NotFoundException();
		}
		removeMultiUserChatService(serviceID);
	}

	public void removeMultiUserChatService(Long serviceID)
			throws NotFoundException {
		RedisMultiUserChatServiceImpl muc = (RedisMultiUserChatServiceImpl) getMultiUserChatService(serviceID);
		if (muc == null) {
			Log.error("MultiUserChatManager: Unable to find service to remove for service ID "
					+ serviceID);
			throw new NotFoundException();
		}
		unregisterMultiUserChatService(muc.getServiceName());
		deleteService(serviceID);
	}

	public RedisMultiUserChatService getMultiUserChatService(Long serviceID) {
		String subdomain = getMultiUserChatSubdomain(serviceID);
		if (subdomain == null)
			return null;
		return mucServices.get(subdomain);
	}

	public RedisMultiUserChatService getMultiUserChatService(String subdomain) {
		return mucServices.get(subdomain);
	}

	public RedisMultiUserChatService getMultiUserChatService(JID jid) {
		String subdomain = jid.getDomain().replace(
				"." + XMPPServer.getInstance().getServerInfo().getXMPPDomain(),
				"");
		return getMultiUserChatService(subdomain);
	}

	public List<RedisMultiUserChatService> getMultiUserChatServices() {
		List<RedisMultiUserChatService> services = new ArrayList<RedisMultiUserChatService>(
				mucServices.values());
		return services;
	}

	public Integer getMultiUserChatServicesCount() {
		return mucServices.size();
	}

	public boolean isServiceRegistered(String subdomain) {
		if (subdomain == null)
			return false;
		return mucServices.containsKey(subdomain);
	}

	public Long getMultiUserChatServiceID(String subdomain) {
		Long id = loadServiceID(subdomain);
		if (id == -1) {
			return null;
		}
		return id;
	}

	public String getMultiUserChatSubdomain(Long serviceID) {
		return loadServiceSubdomain(serviceID);
	}

	/**
	 * 
	 * 加载持久群组信息
	 *
	 */
	private void loadServices() {
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			con = DbConnectionManager.getConnection();
			pstmt = con.prepareStatement(LOAD_SERVICES);
			rs = pstmt.executeQuery();
			while (rs.next()) {
				String subdomain = rs.getString(1);
				String description = rs.getString(2);
				Boolean isHidden = Boolean.valueOf(rs.getString(3));
				RedisMultiUserChatService muc = new RedisMultiUserChatServiceImpl(
						subdomain, description, isHidden);
				if(Log.isInfoEnabled()){
					Log.info("[RedisMultiUserChatService] start MUC:"+subdomain+". success!");
				}
				if (JiveGlobals.getProperty("plugin.redischat.serviceName","redischat").equals(subdomain)) {
					mucServices.put(subdomain, muc);
				}
			}
		} catch (Exception e) {
			Log.error(e.getMessage(), e);
		} finally {
			DbConnectionManager.closeConnection(rs, pstmt, con);
		}
	}

	private long loadServiceID(String subdomain) {
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		Long id = -1L;
		try {
			con = DbConnectionManager.getConnection();
			pstmt = con.prepareStatement(LOAD_SERVICE_ID);
			pstmt.setString(1, subdomain);
			rs = pstmt.executeQuery();
			if (rs.next()) {
				id = rs.getLong(1);
			} else {
				throw new Exception(
						"Unable to locate Service ID for subdomain "
								+ subdomain);
			}
		} catch (Exception e) {
		} finally {
			DbConnectionManager.closeConnection(rs, pstmt, con);
		}
		return id;
	}

	private String loadServiceSubdomain(Long serviceID) {
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		String subdomain = null;
		try {
			con = DbConnectionManager.getConnection();
			pstmt = con.prepareStatement(LOAD_SUBDOMAIN);
			pstmt.setLong(1, serviceID);
			rs = pstmt.executeQuery();
			if (rs.next()) {
				subdomain = rs.getString(1);
			} else {
				throw new Exception(
						"Unable to locate subdomain for service ID "
								+ serviceID);
			}
		} catch (Exception e) {
		} finally {
			DbConnectionManager.closeConnection(rs, pstmt, con);
		}
		return subdomain;
	}

	/**
	 * 
	 * 持久化群组至数据库
	 *
	 * @param subdomain
	 * @param description
	 * @param isHidden
	 */
	private void insertService(String subdomain, String description,
			Boolean isHidden) {
		Connection con = null;
		PreparedStatement pstmt = null;
		Long serviceID = SequenceManager.nextID(JiveConstants.MUC_SERVICE);
		try {
			con = DbConnectionManager.getConnection();
			pstmt = con.prepareStatement(CREATE_SERVICE);
			pstmt.setLong(1, serviceID);
			pstmt.setString(2, subdomain);
			if (description != null) {
				pstmt.setString(3, description);
			} else {
				pstmt.setNull(3, Types.VARCHAR);
			}
			pstmt.setInt(4, (isHidden ? 1 : 0));
			pstmt.executeUpdate();
		} catch (SQLException e) {
			Log.error(e.getMessage(), e);
		} finally {
			DbConnectionManager.closeConnection(pstmt, con);
		}
	}

	/**
	 * 
	 * 更新数据库群组信息
	 *
	 * @param serviceID
	 * @param subdomain
	 * @param description
	 */
	private void updateService(Long serviceID, String subdomain,
			String description) {
		Connection con = null;
		PreparedStatement pstmt = null;
		try {
			con = DbConnectionManager.getConnection();
			pstmt = con.prepareStatement(UPDATE_SERVICE);
			pstmt.setString(1, subdomain);
			if (description != null) {
				pstmt.setString(2, description);
			} else {
				pstmt.setNull(2, Types.VARCHAR);
			}
			pstmt.setLong(3, serviceID);
			pstmt.executeUpdate();
		} catch (SQLException e) {
			Log.error(e.getMessage(), e);
		} finally {
			DbConnectionManager.closeConnection(pstmt, con);
		}
	}

	/**
	 * 
	 * 删除数据库群组信息
	 *
	 * @param serviceID
	 */
	private void deleteService(Long serviceID) {
		Connection con = null;
		PreparedStatement pstmt = null;
		try {
			con = DbConnectionManager.getConnection();
			pstmt = con.prepareStatement(DELETE_SERVICE);
			pstmt.setLong(1, serviceID);
			pstmt.executeUpdate();
		} catch (SQLException e) {
			Log.error(e.getMessage(), e);
		} finally {
			DbConnectionManager.closeConnection(pstmt, con);
		}
	}



	public void markedAsSeniorClusterMember() {
	}

	public void propertySet(String service, String property,
			Map<String, Object> params) {
		CacheFactory.doSynchronousClusterTask(new ServiceUpdatedEvent(service),
				false);
	}

	public void propertyDeleted(String service, String property,
			Map<String, Object> params) {
		CacheFactory.doSynchronousClusterTask(new ServiceUpdatedEvent(service),
				false);
	}

	public void userCreated(User user, Map<String, Object> params) {
	}

	public void userDeleting(User user, Map<String, Object> params) {
		MUCPersistenceManager.removeAffiliationFromDB(XMPPServer.getInstance()
				.createJID(user.getUsername(), null, true));
	}

	public void userModified(User user, Map<String, Object> params) {
	}

	private static class ServiceComparator implements
			Comparator<MultiUserChatService> {
		public int compare(MultiUserChatService o1, MultiUserChatService o2) {
			return o1.getServiceName().compareTo(o2.getServiceName());
		}
	}

}
